//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <drvpub.h>
#include "display_def.h"

static void LcdParamInit(DisplayContext *pCxt)
{
    pCxt->basePlaneFormat = BASEPLANE_COLORFORMAT;
    pCxt->vaBasePlane = NULL;
    pCxt->overlay[1].Enable = FALSE;
    pCxt->overlay[2].Enable = FALSE;
}

static void SetDisplayDesc(DisplayContext *pCxt)
{
    // Alloc Buffer for all channel DMA descriptors, 1 page OK.
    pCxt->vaChan0Desc = (UInt32)Drv_AllocBuffer(1, 0);
    pCxt->paChan0Desc =
        Drv_GetBufferPhysicalAddress((void *)pCxt->vaChan0Desc);

    pCxt->vaChan1Desc = pCxt->vaChan0Desc  + DESC_SIZE;
    pCxt->vaChan2Desc = pCxt->vaChan1Desc  + DESC_SIZE;
    pCxt->vaChan3Desc = pCxt->vaChan2Desc  + DESC_SIZE;
    pCxt->vaChan4Desc = pCxt->vaChan3Desc  + DESC_SIZE;
    pCxt->vaChan1AltDesc = pCxt->vaChan4Desc + DESC_SIZE;
    pCxt->vaPaletteDesc = pCxt->vaChan1AltDesc + DESC_SIZE;

    pCxt->paChan1Desc = pCxt->paChan0Desc  + DESC_SIZE;
    pCxt->paChan2Desc = pCxt->paChan1Desc  + DESC_SIZE;
    pCxt->paChan3Desc = pCxt->paChan2Desc  + DESC_SIZE;
    pCxt->paChan4Desc = pCxt->paChan3Desc  + DESC_SIZE;
    pCxt->paChan1AltDesc = pCxt->paChan4Desc + DESC_SIZE;
    pCxt->paPaletteDesc = pCxt->paChan1AltDesc + DESC_SIZE;
}

// If bFirst == TRUE, allocate the main buffer of baseplane
// Else allocate the temporary buffer of baseplane
static ECode SetBaseplaneBuffer(DisplayContext *pCxt, UInt32 flag, Boolean bFirst)
{
    // Alloc Buffer for baseplane framebuffer
    pCxt->vaBasePlane =
        (unsigned int)Drv_AllocBuffer(BASEPLANE_FB_PAGES, flag);
    if (!pCxt->vaBasePlane) {
        DBGOUT(DBG_ERR, ("Alloc baseplane buffer error!\n"));
        return E_OUT_OF_MEMORY;
    }

    pCxt->paBasePlane =
        Drv_GetBufferPhysicalAddress((void *)pCxt->vaBasePlane);

    if (bFirst) {
        pCxt->vaBasePlaneTemp = pCxt->vaBasePlane;
        pCxt->paBasePlaneTemp = pCxt->paBasePlane;
    }
    return NOERROR;
}

static void FreeBaseplaneTempBuffer(DisplayContext *pCxt)
{
    void *pTempBuf = (void *)pCxt->vaBasePlane;

    memcpy((UInt16 *)pCxt->vaBasePlaneTemp,
        (UInt16 *)pCxt->vaBasePlane, pCxt->uChan0Size);

    pCxt->vaBasePlane = pCxt->vaBasePlaneTemp;
    pCxt->paBasePlane = pCxt->paBasePlaneTemp;

    // buffer is changed, so let LCD DMA start again
    LcdCtrl_Suspend(pCxt, 0);
    LcdCtrl_Resume(pCxt);

    Drv_FreeBuffer(pTempBuf, BASEPLANE_FB_PAGES);
}

#if 0
static void ClearBaseplane(DisplayContext *pCxt)
{
    memset((void *)pCxt->vaBasePlane, 0x0, pCxt->uChan0Size);
}
#endif

static void ClearOverlay1(DisplayContext *pCxt, ColorFormat color)
{
    UInt32 i;
    if (color == COLOR_RGBT666) {
        for (i = 0; i < pCxt->uChan1Size / 4; i++) {
            ((UInt32 *)pCxt->vaOverlay1)[i] = 0x40000;
        }
    }
    else if (color == COLOR_RGB565) {
        memset((void *)pCxt->vaOverlay1, 0, pCxt->uChan1Size);
    }
}

static void ClearOverlay2(DisplayContext *pCxt)
{
    // Clear overlay2 framebuffer
    memset((void *)pCxt->vaOverlay2Y, 0, OVERLAY2Y_FB_MAXSIZE);
    memset((void *)pCxt->vaOverlay2Cb, 0x80, OVERLAY2Cb_FB_MAXSIZE);
    memset((void *)pCxt->vaOverlay2Cr, 0x80, OVERLAY2Cr_FB_MAXSIZE);
}

static void Convert_RGB565ToRGBT555(DisplayContext *pCxt)
{
    UInt16 *pBufDst = (UInt16 *)pCxt->vaBasePlane;
    UInt16 *pBufSrc = (UInt16 *)pCxt->vaBasePlaneTemp;
    int bufCnt = pCxt->uChan0Size / 2;
    int i;

    for (i = 0; i < bufCnt; i++) {
        UInt16 temp = pBufSrc[i];
        pBufDst[i] = ((temp & 0xffc0) >> 1) | (temp & 0x1f);
    }
}

static void Convert_RGBT555ToRGB565(DisplayContext *pCxt)
{
    UInt16 *pBufDst = (UInt16 *)pCxt->vaBasePlane;
    UInt16 *pBufSrc = (UInt16 *)pCxt->vaBasePlaneTemp;
    int bufCnt = pCxt->uChan0Size / 2;
    int i;

    for (i = 0; i < bufCnt; i++) {
        UInt16 temp = pBufSrc[i];
        pBufDst[i] = ((temp & 0x7fe0) << 1) | (temp & 0x1f);
    }
}

static void GetOverlayInfo(DisplayContext *pCxt, int layer)
{
    assert(layer == 1 || layer == 2);
    OverlayFormat *pOverlay = &pCxt->overlay[layer];

    if (layer == 1) {
        // Overlay1 framebuffer address used in kernel
        pOverlay->pChannelFb[0] = (void*)pCxt->vaOverlay1;
        pOverlay->ChannelSize[0] = pCxt->uChan1Size;
    }
    else {
        // Overlay2 framebuffer address used in kernel
        pOverlay->pChannelFb[0] = (void*)pCxt->vaOverlay2Y;
        pOverlay->pChannelFb[1] = (void*)pCxt->vaOverlay2Cb;
        pOverlay->pChannelFb[2] = (void*)pCxt->vaOverlay2Cr;
        pOverlay->ChannelSize[0] = pCxt->uChan2Size;
        pOverlay->ChannelSize[1] = pCxt->uChan3Size;
        pOverlay->ChannelSize[2] = pCxt->uChan4Size;
    }
}

void *Display_GetBasePlane(DisplayContext *pCxt)
{
    return (void *)pCxt->vaBasePlane;
}

ECode Display_Create(DisplayContext *pCxt)
{
    SetDisplayDesc(pCxt);
    LcdParamInit(pCxt);
    return SetBaseplaneBuffer(pCxt, DRVBUF_SPEED_FAST, TRUE);
}

ECode Display_Init(DisplayContext *pCxt)
{
    pCxt->uChan0Size = BASEPLANE_FB_SIZE;
    pCxt->overlayType = OVERLAYTYPE_UNDERLAY;
    pCxt->baseChangeRefCnt = 0;

#if 1
    // This loop is only used for test program that will initialize
    // display more than 1 time.
    int i;
    for (i = 1; i <= 2; i++) {
        if (pCxt->overlay[i].Enable) {
            Display_DisableOverlay(pCxt, i);
        }
    }
#endif

    // Before init, disable LCD controller
    LcdCtrl_Disable(pCxt);

    // Initialize the GPIO registers for proper LCD Controller operation
    //Lcm_SetupGPIOs(pCxt);

    // Open back light
    //BackLight_Open(pCxt);

#ifdef _OLD_LCD_DRIVER
    Lcm_DisplayOn(pCxt, 1);
#else
    //Lcm_DisplayOn(pCxt, 1);
#endif

    // Initialize the LCD Controller and frame descriptors
    LcdCtrl_Init(pCxt);

    // Enable the LCD controller
    LcdCtrl_Enable(pCxt);

    //BackLight_Open(pCxt);

    //ClearBaseplane(pCxt);

    return NOERROR;
}

static void LcdGetOverlay2Size(DisplayContext *pCxt, OverlayFormat *pOverlay)
{
    unsigned int pixels = pOverlay->Height * pOverlay->Width;
    unsigned int DMALength = 0;

    // Now only support COLOR_YCbCr422 & COLOR_YCbCr420 in overlay2
    assert (pOverlay->Color == COLOR_YCbCr422 ||
            pOverlay->Color == COLOR_YCbCr420);

    if (pOverlay->Color == COLOR_YCbCr422) {
        DMALength = pixels;
        if ((DMALength % 4) > 0) {
            // 16 bits total
            DMALength = (((32 - ((pixels << 3) % 32)) + (pixels << 3)) >> 3);
        }
        pCxt->uChan2Size = DMALength;

        DMALength = pixels >> 1;
        if (((pixels << 2) % 32) > 0) {
            DMALength = (((32 - ((pixels << 2) % 32)) + (pixels << 2)) >> 3);
        }
        pCxt->uChan3Size = DMALength;
        pCxt->uChan4Size = DMALength;
    }
    else if (pOverlay->Color == COLOR_YCbCr420) {
        DMALength = pixels;
        if ((DMALength % 4) > 0) {
            // 12 bits total
            DMALength = (((32 - ((pixels << 3) % 32)) + (pixels << 3)) >> 3);
        }
        pCxt->uChan2Size = DMALength;

        DMALength = pixels >> 2;
        if (((pixels << 1) % 32) > 0) {
            DMALength = (((32 - ((pixels << 1) % 32)) + (pixels << 1)) >> 3);
        }
        pCxt->uChan3Size = DMALength;
        pCxt->uChan4Size = DMALength;
    }
}

ECode Display_SetOverlay(DisplayContext *pCxt, OverlayFormat *pOverlay)
{
    ECode ec;
    int layer = pOverlay->Layer;
    Boolean bBaseplaneChanged = FALSE;

    if (layer != 1 && layer != 2) {
        ec = E_NOT_IMPLEMENTED;
        goto Exit;
    }

    // For YCbCr422 & YCbCr420: Width >= 2.
    // For YCbCr420: Height >= 2.
    // We bring a uniform standard for all overlay settings.
    if (pOverlay->Width < 2 || pOverlay->Width > LCD_WIDTH ||
        pOverlay->Height < 2 || pOverlay->Height > LCD_HEIGHT ||
        pOverlay->PosX < 0 || pOverlay->PosX + pOverlay->Width > LCD_WIDTH ||
        pOverlay->PosY < 0 || pOverlay->PosY + pOverlay->Height > LCD_HEIGHT) {
        ec = E_INVALID_ARGUMENT;
        goto Exit;
    }

    if (layer == 1) {
        if (pCxt->overlay[2].Enable &&
            pOverlay->Flag != pCxt->overlay[2].Flag) {
            DBGOUT(DBG_ERR, ("Overlay1 Flag should equal Overlay2 Flag\n"));
            ec = E_ACCESS_DENIED;
            goto Exit;
        }
        if (pOverlay->Color == COLOR_RGBT666) {
            // Every pixel occupies 32 bits
            pCxt->uChan1Size =(pOverlay->Width * pOverlay->Height) << 2;
        }
        else if (pOverlay->Color == COLOR_RGB565) {
            // Every pixel occupies 16 bits
            pCxt->uChan1Size =(pOverlay->Width * pOverlay->Height) << 1;
        }
        else {
            // Add more formats when needed
            ec = E_NOT_IMPLEMENTED;
            goto Exit;
        }
        if (pCxt->overlay[1].Enable) {
            ec = E_ACCESS_DENIED;
            goto Exit;
        }

        pCxt->vaOverlay1 = (UInt32)Drv_AllocBuffer(
            ROUNDUP_PAGE(pCxt->uChan1Size) >> PAGE_SHIFT, 0);
        if (!pCxt->vaOverlay1) {
            DBGOUT(DBG_ERR, ("Alloc overlay1 buffer error!\n"));
            ec = E_OUT_OF_MEMORY;
            goto Exit;
        }
        pCxt->paOverlay1 =
            Drv_GetBufferPhysicalAddress((void *)pCxt->vaOverlay1);
    }
    else {
        if (pCxt->overlay[1].Enable &&
            pOverlay->Flag != pCxt->overlay[1].Flag) {
            DBGOUT(DBG_ERR, ("Overlay2 Flag should equal Overlay1 Flag\n"));
            ec = E_ACCESS_DENIED;
            goto Exit;
        }

#ifndef OVERLAY2_CAN_REENABLE
        if (pCxt->overlay[2].Enable) {
            ec = E_ACCESS_DENIED;
            goto Exit;
        }
#endif

        // Channel size for layer 2 will be calculated in LcdGetDMALength
        LcdGetOverlay2Size(pCxt, pOverlay);

// Set overlay2 again without disable it may lead to error sometimes
#ifdef OVERLAY2_CAN_REENABLE
        // Overlay2 can be reset without disable it.
        // (For fulfilling app's requirement)
        if (pCxt->overlay[2].Enable) goto SetOverlay;
#endif

        // Allocate buffer for overlay2.
        // Note 3 channels are all allocated for overlay2
        // when using YCbCr format.
        if (pOverlay->Color != COLOR_YCbCr422 &&
            pOverlay->Color != COLOR_YCbCr420) {
            ec = E_NOT_IMPLEMENTED;
            goto Exit;
        }

        pCxt->vaOverlay2Y = (unsigned int)Drv_AllocBuffer(
            OVERLAY2_FB_PAGES, 0);
        if (!pCxt->vaOverlay2Y) {
            DBGOUT(DBG_ERR, ("Alloc overlay2 buffer error!\n"));
            ec = E_OUT_OF_MEMORY;
            goto Exit;
        }
        pCxt->paOverlay2Y =
            Drv_GetBufferPhysicalAddress((void *)pCxt->vaOverlay2Y);

        pCxt->paOverlay2Cb = pCxt->paOverlay2Y + OVERLAY2Y_FB_MAXSIZE;
        pCxt->paOverlay2Cr = pCxt->paOverlay2Cb + OVERLAY2Cb_FB_MAXSIZE;
        pCxt->vaOverlay2Cb = pCxt->vaOverlay2Y + OVERLAY2Y_FB_MAXSIZE;
        pCxt->vaOverlay2Cr = pCxt->vaOverlay2Cb + OVERLAY2Cb_FB_MAXSIZE;
    }

    if ((pOverlay->Flag & OVERLAYTYPE_MASK) == OVERLAYTYPE_UNDERLAY) {
        // When use underlay, the color format of baseplane shoule be
        // changed to RGBT555
        if (pCxt->baseChangeRefCnt == 0) {
            DBGOUT(DBG_OVERLAY, ("SetOverlay: Temp change baseplane buffer\n"));
            // Alloc Buffer for temp baseplane framebuffer
            ec = SetBaseplaneBuffer(pCxt, 0, FALSE);
            if (FAILED(ec)) goto Exit;

            ec = Display_ChangeBasePlaneFormat(pCxt, COLOR_RGBT555);
            if (FAILED(ec)) goto Exit;

            bBaseplaneChanged = TRUE;
        }
        pCxt->baseChangeRefCnt++;
    }

#ifdef OVERLAY2_CAN_REENABLE
SetOverlay:
#endif

    pCxt->overlayType = pOverlay->Flag & OVERLAYTYPE_MASK;

    if (layer == 1) ClearOverlay1(pCxt, pOverlay->Color);
    else    ClearOverlay2(pCxt);

    LcdCtrl_OverlayResetController(pCxt, pOverlay);
    if (bBaseplaneChanged) {
        DBGOUT(DBG_OVERLAY, ("SetOverlay: Restore baseplane buffer\n"));
        FreeBaseplaneTempBuffer(pCxt);
    }

    ec = LcdCtrl_SetOverlay(pCxt, pOverlay);

Exit:

    if (SUCCEEDED(ec)) {
        memcpy(&pCxt->overlay[layer], pOverlay, sizeof(OverlayFormat));
        GetOverlayInfo(pCxt, layer);
    }

    return ec;

}

void Display_DisableOverlay(DisplayContext *pCxt, int layer)
{
    OverlayFormat *pDrvOverlay;
    void *KernelBufAddr;
    int nPages;
    Boolean bBaseplaneChanged = FALSE;

    pDrvOverlay = &pCxt->overlay[layer];
    if (layer == 1) {
        KernelBufAddr = (void *)pCxt->vaOverlay1;
        nPages = ROUNDUP_PAGE(pCxt->uChan1Size) >> PAGE_SHIFT;
    }
    else if (layer == 2) {
        KernelBufAddr = (void *)pCxt->vaOverlay2Y;
        nPages = OVERLAY2_FB_PAGES;
    }
    else
        return;

    if(pDrvOverlay->Enable == FALSE) return;
    pDrvOverlay->Enable = FALSE;

    LcdCtrl_DisableOverlay(pCxt, layer);

    if (pCxt->overlayType == OVERLAYTYPE_UNDERLAY) {
        pCxt->baseChangeRefCnt--;
        if (pCxt->baseChangeRefCnt == 0) {
            // Alloc Buffer for temp baseplane framebuffer
            DBGOUT(DBG_OVERLAY,
                ("DisableOverlay: Temp change baseplane buffer\n"));
            ECode ec = SetBaseplaneBuffer(pCxt, 0, FALSE);
            if (SUCCEEDED(ec)) {
                // The color format of baseplane shoule be changed to RGB565
                Display_ChangeBasePlaneFormat(pCxt, COLOR_RGB565);
                bBaseplaneChanged = TRUE;
            }
        }
    }

    LcdCtrl_OverlayResetController(pCxt, NULL);

     if (bBaseplaneChanged) {
        DBGOUT(DBG_OVERLAY, ("DisableOverlay: Restore baseplane buffer\n"));
        FreeBaseplaneTempBuffer(pCxt);
    }

    Drv_FreeBuffer(KernelBufAddr, nPages);

}

void Display_GetOverlayFormat(DisplayContext *pCxt, int layer,
    OverlayFormat *pOverlay)
{
    memcpy(pOverlay, &pCxt->overlay[layer], sizeof(OverlayFormat));
}

// Special use
ECode Display_ChangeBasePlaneFormat(DisplayContext *pCxt, ColorFormat format)
{
    if (pCxt->basePlaneFormat == COLOR_RGB565) {
        if (format == COLOR_RGBT555) {
            pCxt->basePlaneFormat = COLOR_RGBT555;
            Convert_RGB565ToRGBT555(pCxt);
            goto CanSet;
        }
    }
    else if (pCxt->basePlaneFormat == COLOR_RGBT555) {
        if (format == COLOR_RGB565) {
            pCxt->basePlaneFormat = COLOR_RGB565;
            Convert_RGBT555ToRGB565(pCxt);
            goto CanSet;
        }
    }

// CannotSet:
    return E_ACCESS_DENIED;

CanSet:
    return NOERROR;
}

//
// Power management
//

void Display_Suspend(DisplayContext *pCxt, int type, int lcdOff)
{
    if (1/*lcdOff*/) {
        Lcm_DisplayOff(pCxt);
    }
    LcdCtrl_Suspend(pCxt, type);
}

void Display_Resume(DisplayContext *pCxt, int lcdOn)
{
    LcdCtrl_Resume(pCxt);
    if (1/*lcdOn*/) {
        Lcm_DisplayOn(pCxt, lcdOn);
    }
}
